//<%@ taglib prefix="jj" uri="http://jaxson.org/base" %>

//<jj:import>
org.jaxson.util.collections.List;
org.jaxson.util.collections.Map;
//</jj:import>

namespace( "org.jaxson.util.http" );

/**
 * Wrapper around a client side javascript XMLHttpRequest.
 * Includes convenience methods for setting form parameters by object,
 * creating request parameters from an existing form, setting http request headers,
 * and other features.
 */
org.jaxson.util.http.XHR = function(){
	
	var xmlhttp = false;
	var that = this;
	var synchronous = false;
	var errorState = false;
	var headers = {};
	var method = "GET";
	var params = {};
	var formEl;
	var chunkbuffer = "";
	var rawHeaders = "";
	var headers = [];
	var chunkedBytesRead = 0;
	var lager;
	var uri;
	var timeout = 30000;
	//var lager = new Logger( "org.jjax.util.http.XHR", Logger.LEVEL_TRACE );
	//alert( Logger );
	
	
	// create the XMLHttpRequest
	var that = this;
	
	function createRawXHR(){
		var result;
		
		try { result = new XMLHttpRequest(); } catch(e) {}
		try { if( !result )result = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
		try { if( !result )result = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
		
		if( !result ){
			//alert( "This feature isn't supported in your browser." );
			throw "XmlHttpRequest object unavailable";
		}
		
		return result;
	}
	
	xmlhttp = createRawXHR();

	function getRequestParameters(){
		if( typeof( params ) === "string" )
			return params;

		var result = "";
		for( i in params ){
			result += i + "=" + params[i] + "&";
		}
		
		return result.indexOf("&") != -1?result.substring(0, result.length -1):result;
	}
	
	function parseHeaders( rawHeaders ){
		var headers = {};
		
		try{
			var tmp = rawHeaders.split( "\n" );
			for( i=0; i<tmp.length; i++ ){
				var line = tmp[i];
				var name = tmp[i].split(":")[0].trim();
				var val =  tmp[i].split(":")[1].trim();
				headers[name] = val;
			}
		}
		catch( e ){
			//if( lager !== undefined && lager !== null )
			//	lager.error( e );
		}
	
		return headers;
	}
	
	var me={
		onerror : function(e){
		
			if( window['console'] ){
				
				if( window.console.log ){
					console.log( "onerror called for request to " + uri );
					console.log( e );
				}
				
				if( window.console.dir ){
					console.dir( e );
				}
				
			}
		},
		
		// calbacks.  Can be overwritten
		oncomplete : function(){},
		onopen : function(){},
		onsend : function(){},
		onloaded : function(){},
		onloading : function(){},
		onprogress : function(){},
		onreadystatechange : function(){},
		onsuccess : function(){},
		onrecieved : function(){},
		
		// accessors
		setMethod : function( m ){method = m;},
		setSynchronous : function( bool ){synchronous = bool;},
		inErrorState : function(){return errorState;},
		setRequestParameters: function( parameters ){params = parameters;},
		setLogger : function( logger ){lager = logger;},
		setTimeout : function( t ){timeout = timeout;},
		getAllResponseHeaders : function(){return xmlhttp.getAllResponseHeaders();},
		getResponseHeaders : function(){return headers;},
		getReadyState : function(){return xmlhttp.readyState;},
		getXMLHttpRequest : function(){return xmlhttp;},
		getStatusCode : function(){return xmlhttp.status;},
		
		doRequest : function( puri ){
			
			if( !lager )
				lager = new Logger( "org.jjax.util.http.XHR", Logger.LEVEL_INFO);
			
			uri = puri;
			lager.trace( "doRequest called", puri, method );
			
			var lastReadyState = 0; // fix for IE8 not calling onerror when the server is down!
			function handleRequest(){
				lager.trace( "handleRequest....", puri, method, method.toString().toLowerCase() === "post" );
				me.onopen();
			
				var to = setTimeout( function(){
					me.onerror();
					errorState = true;
				}, timeout );
	
				lager.trace( "opening...." );
				// post
				if( method.toString().toLowerCase() === "post" ){
					lager.trace( "preparing post to " + puri );
					xmlhttp.open( method, uri, !synchronous );
					xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
					xmlhttp.setRequestHeader("Content-length", getRequestParameters().length);
					xmlhttp.setRequestHeader("Connection", "close");
				}
				// GET not working???
				// get, head, etc
				else{
					lager.trace( "handling request method other than POST" );
					xmlhttp.open( method, uri /* + '?' + getRequestParameters()*/, synchronous );
				}

				lager.trace( "done opening...." );
				// set headers here.
				
				// javascript closures are fun!
				// create a wrapper around 'this' to have proper closure in the XHR callback.
				//var that = this;
				var that = me;
				xmlhttp.onreadystatechange = function(){
					that.onreadystatechange();
					
					lager.trace( "readyState=" + xmlhttp.readyState );
					
					if( RuntimeInformation.isIE() && lastReadyState != xmlhttp.readyState-1 ){
						that.onerror({msg: "connection failure to " + uri});
						lastReadyState = 0;
						return;
					}
					
					lastReadyState = xmlhttp.readyState;
					
					// http://msdn.microsoft.com/en-us/library/ms753800(VS.85).aspx
					// LOADING The object has been created, but the send method has not been called. 
					if( xmlhttp.readyState === 1 ) 
						that.onloading();
					
					// LOADED The send method has been called, but the status and headers are not yet available.
					if( xmlhttp.readyState === 2 )
						that.onloaded();
					
					// INTERACTIVE Some data has been received. 
					// Calling the responseBody and responseText properties at this state to obtain partial 
					// results will return an error, because status and response headers are not fully available.
					if( xmlhttp.readyState === 3 ){
						
						// substring the response text
						// to get only the current chunk
						if( that.isChunked() ){
//							try{
//								console.log( xmlhttp.responseBody );
//							}
//							catch(e){
//								console.log( e );
//							}
//							var tmp = xmlhttp.responseText;
//							chunkbuffer = tmp.substring( chunkedBytesRead, tmp.length );
//							chunkedBytesRead = tmp.length;
//							that.onrecieved();
							//that.oncomplete();
						}
						
						that.onprogress();
					}
				
					// COMPLETED All the data has been received, and the complete data is available in the 
					// responseBody and responseText properties.
					if( xmlhttp.readyState === 4 ){
						clearTimeout( to );
						if( !that.isChunked ){
							var rawHeaders = xmlhttp.getAllResponseHeaders();
							var parsedHeaders = parseHeaders( rawHeaders );
							headers = parsedHeaders;
						}
						
						lastReadyState = 0;
						that.responseText = xmlhttp.responseText;
						
						try{
							that.onrecieved();
							that.oncomplete();
							
							if( xmlhttp.status != 200 ){
								if( lager ){
									lager.trace( "error handling " + puri, "status=" + xmlhttp.status );
								}
								errorState = true;
								me.onerror();
							}
							else{
								me.onsuccess();
							}
							
						}
						catch( e ){
							lager.error( e );
						}
					}
				};
				
				that.onsend();
				lager.trace( "sending request" );
				xmlhttp.send(method.toString().toLowerCase() === "get"?null:getRequestParameters());
				/*
				*/
			}
			
			/*
			// first do a HEAD request to check if the response is chunked
			var xhdr = createRawXHR();
			xhdr.open("HEAD", uri, true);
			xhdr.onreadystatechange=function() {
				
				if (xhdr.readyState==4) {
					
					// read headers
					var rawHeaders = xhdr.getAllResponseHeaders();
					var parsedHeaders = parseHeaders( rawHeaders );
					headers = parsedHeaders;
					
					// do the request.
					handleRequest();
				}
			}
			xhdr.send(null);
			*/
		
			lager.trace( "calling handleRequest" );	
			handleRequest(); 
		},
		
		getResponseText: function(){
			
			if( !this.isChunked() )
				return xmlhttp.responseText;
			else
				return chunkbuffer;
		},
		
		evalResponse : function(){
			try{
				eval( "var result=" + xmlhttp.responseText );
				return result;
			}
			catch( e ){
				return null;
			}
		},
		
		isChunked : function(){
			return headers["Transfer-Encoding"] !== undefined && 
					headers["Transfer-Encoding"].indexOf( "chunked" ) != -1;
		},
		
		abort : function(){
			try{
				xmlhttp.abort();
			}
			catch(e){
				if( console !== undefined ){
					console.error( e );
				}
			}
		}
	};
	
	return me;
};
